/******************************************************************************
 *
 * package:     Log4Qt
 * file:        log4qttest.cpp
 * created:     September 2007
 * author:      Martin Heinrich
 *
 *
 * Copyright 2007 Martin Heinrich
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ******************************************************************************/


#include "log4qttest.h"

#include "log4qt/basicconfigurator.h"
#include "log4qt/consoleappender.h"
#include "log4qt/dailyrollingfileappender.h"
#include "log4qt/fileappender.h"
#include "log4qt/helpers/configuratorhelper.h"
#include "log4qt/helpers/datetime.h"
#include "log4qt/helpers/factory.h"
#include "log4qt/helpers/initialisationhelper.h"
#include "log4qt/helpers/optionconverter.h"
#include "log4qt/helpers/patternformatter.h"
#include "log4qt/helpers/properties.h"
#include "log4qt/logmanager.h"
#include "log4qt/loggerrepository.h"
#include "log4qt/patternlayout.h"
#include "log4qt/propertyconfigurator.h"
#include "log4qt/rollingfileappender.h"
#include "log4qt/simplelayout.h"
#include "log4qt/ttcclayout.h"
#include "log4qt/varia/denyallfilter.h"
#include "log4qt/varia/levelmatchfilter.h"
#include "log4qt/varia/levelrangefilter.h"
#include "log4qt/varia/stringmatchfilter.h"

#include <QBuffer>
#include <QBitArray>
#include <QFile>
#include <QMetaEnum>
#include <QSettings>
#include <QTextStream>
#include <QThread>
#include <QtTest/QtTest>

#include <type_traits>

using namespace Log4Qt;
#if QT_VERSION >= 0x050E00
using Qt::endl;
#endif

QTEST_MAIN(Log4QtTest)
LOG4QT_DECLARE_STATIC_LOGGER(test_logger, Test::TestLog4Qt)

Log4QtTest::Log4QtTest(QObject *parent) : QObject(parent),
    mSkipLongTests(false),
    mProperties(&mDefaultProperties)
{
#ifdef LOG4QT_DISABLE_LONG_TESTS
    mSkipLongTests = true;
#endif
}

Log4QtTest::~Log4QtTest() = default;

void Log4QtTest::initTestCase()
{
    // Logging
    LogManager::resetConfiguration();

    // File system
    QString name = QDir::tempPath() + "/Log4QtTest_"
                   + QDateTime::currentDateTime().toString(QStringLiteral("yyyyMMdd_hhmmsszzz"));
    if (!mTemporaryDirectory.mkdir(name))
        QFAIL("Creation of temporary directory failed");
    mTemporaryDirectory.setPath(name);
    qDebug() << "Using temporaray directory: " << mTemporaryDirectory.path();

    // Appender to track events generated by Log4Qt
    mpLoggingEvents.reset(new Log4Qt::ListAppender(this));
    mpLoggingEvents->setName(QStringLiteral("Log4QtTest"));
    loggingEvents()->setConfiguratorList(true);
    resetLogging();
}


void Log4QtTest::cleanupTestCase()
{
    LogManager::resetConfiguration();

    QTest::qWait(1000);
    if (!deleteDirectoryTree(mTemporaryDirectory.path()))
        QFAIL("Cleanup of temporary directory failed");
}

void Log4QtTest::DateTime_alternativelyFormat_data()
{
    QTest::addColumn<QString>("format");
    QTest::addColumn<QDateTime>("datetime");
    QTest::addColumn<QString>("datetimestring");
    QDateTime reference(QDate(2016, 5, 3), QTime(15, 7, 5, 9));
    qint64 diffTime = reference.toMSecsSinceEpoch() - InitialisationHelper::startTime();
    QTest::newRow("EMPTY") << QString() << reference << QString();
    QTest::newRow("INVALID") << QStringLiteral("ISO8601") << QDateTime() << QString();
    QTest::newRow("NONE") << QStringLiteral("NONE") << reference << QString();
    QTest::newRow("RELATIVE") << QStringLiteral("RELATIVE") << reference << QString::number(diffTime);
    QTest::newRow("ISO8601") << QStringLiteral("ISO8601") << reference << QStringLiteral("2016-05-03 15:07:05.009");
    QTest::newRow("ABSOLUTE") << QStringLiteral("ABSOLUTE") << reference << QStringLiteral("15:07:05.009");
    QTest::newRow("DATE") << QStringLiteral("DATE") << reference << QStringLiteral("03 05 2016 15:07:05.009");
    QTest::newRow("CUSTOM1") << QStringLiteral("yyyy-MM-dd hh:mm:ss.zzz") << reference << QStringLiteral("2016-05-03 15:07:05.009");
}

void Log4QtTest::DateTime_alternativelyFormat()
{
    QFETCH(QString, format);
    QFETCH(QDateTime, datetime);
    QFETCH(QString, datetimestring);

    QCOMPARE(DateTime(datetime).toString(format), datetimestring);
}

void Log4QtTest::DateTime_milliseconds_data()
{
    QTest::addColumn<QDateTime>("datetime");
    QTest::addColumn<qint64>("milliseconds");

    QTest::newRow("2001-09-07 15:07:05.009") << QDateTime(QDate(2001, 9, 7), QTime(15, 7, 5, 9), Qt::UTC) << Q_INT64_C(999875225009);
}

void Log4QtTest::DateTime_milliseconds()
{
    QFETCH(QDateTime, datetime);
    QFETCH(qint64, milliseconds);

    QCOMPARE(DateTime(datetime).toMSecsSinceEpoch(), milliseconds);
    QCOMPARE(DateTime::fromMSecsSinceEpoch(milliseconds).toUTC(), datetime);
}

void Log4QtTest::PatternFormatter_data()
{
    QTest::addColumn<LoggingEvent>("event");
    QTest::addColumn<QString>("pattern");
    QTest::addColumn<QString>("result");
    QTest::addColumn<int>("event_count");

    // Create end of line
    QString eol;
#ifdef Q_OS_WIN
    eol = QStringLiteral("\n");
#else
    eol = QStringLiteral("\n");
#endif
    // Prepare event data
    int relative_offset = 17865;
    qint64 relative_timestamp = InitialisationHelper::startTime() + relative_offset;
    QString relative_string = QString::number(relative_offset);
    qint64 absolute_timestamp =
        DateTime(QDateTime(QDate(2001, 9, 7), QTime(15, 7, 5, 9))).toMSecsSinceEpoch();
    QHash<QString, QString> properties;
    properties.insert(QStringLiteral("A"), QStringLiteral("a"));
    properties.insert(QStringLiteral("B"), QStringLiteral("b"));
    properties.insert(QStringLiteral("C"), QStringLiteral("c"));

    QTest::newRow("Default conversion")
            << LoggingEvent(test_logger(),
                            Level(Level::DEBUG_INT),
                            QStringLiteral("This is the message"))
            << "%m%n"
            << "This is the message" + eol
            << 0;
    QTest::newRow("TTCC conversion")
            << LoggingEvent(test_logger(),
                            Level(Level::DEBUG_INT),
                            QStringLiteral("This is the message"),
                            QStringLiteral("NDC"),
                            properties,
                            QStringLiteral("main"),
                            relative_timestamp)
            << "%r [%t] %p %c %x - %m%n"
            << relative_string + " [main] DEBUG Test::TestLog4Qt NDC - This is the message" + eol
            << 0;
    QTest::newRow("Java class documentation example 1")
            << LoggingEvent(test_logger(),
                            Level(Level::WARN_INT),
                            QStringLiteral("This is the message"),
                            QStringLiteral("NDC"),
                            properties,
                            QStringLiteral("main"),
                            relative_timestamp)
            << "%-5p [%t]: %m%n"
            << "WARN  [main]: This is the message" + eol
            << 0;
    QTest::newRow("Java class documentation example 2")
            << LoggingEvent(test_logger(),
                            Level(Level::INFO_INT),
                            QStringLiteral("This is the message"),
                            QStringLiteral("NDC"),
                            properties,
                            QStringLiteral("main"),
                            relative_timestamp)
            << "%r [%t] %-5p %c %x - %m%n"
            << relative_string + " [main] INFO  Test::TestLog4Qt NDC - This is the message" + eol
            << 0;
    QTest::newRow("Java class documentation example 2")
            << LoggingEvent(test_logger(),
                            Level(Level::INFO_INT),
                            QStringLiteral("This is the message"),
                            QStringLiteral("NDC"),
                            properties,
                            QStringLiteral("main"),
                            relative_timestamp)
            << "%-6r [%15.15t] %-5p %30.30c %x - %m%n"
            << relative_string + "  [           main] INFO                Test::TestLog4Qt NDC - This is the message" + eol
            << 0;
    QTest::newRow("Java class documentation example 2, truncating")
            << LoggingEvent(test_logger(),
                            Level(Level::INFO_INT),
                            QStringLiteral("This is the message"),
                            QStringLiteral("NDC"),
                            properties,
                            QStringLiteral("threadwithextralongname"),
                            relative_timestamp)
            << "%-6r [%15.15t] %-5p %30.30c %x - %m%n"
            << relative_string + "  [thextralongname] INFO                Test::TestLog4Qt NDC - This is the message" + eol
            << 0;
    QTest::newRow("TTCC with ISO date")
            << LoggingEvent(test_logger(),
                            Level(Level::DEBUG_INT),
                            QStringLiteral("This is the message"),
                            QStringLiteral("NDC"),
                            properties,
                            QStringLiteral("main"),
                            absolute_timestamp)
            << "%d{ISO8601} [%t] %p %c %x - %m%n"
            << "2001-09-07 15:07:05.009 [main] DEBUG Test::TestLog4Qt NDC - This is the message" + eol
            << 0;
    QTest::newRow("TTCC conversion with file, line and method")
            << LoggingEvent(test_logger(),
                            Level(Level::DEBUG_INT),
                            QStringLiteral("This is the message"),
                            QStringLiteral("NDC"),
                            properties,
                            QStringLiteral("main"),
                            relative_timestamp,
                            MessageContext("foo.cpp", 100, "foo()"),
                            QStringLiteral("category")
                            )
            << "%r [%t] %p %c %F:%L-%M %x - %m%n"
            << relative_string + " [main] DEBUG Test::TestLog4Qt foo.cpp:100-foo() NDC - This is the message" + eol
            << 0;
    QTest::newRow("TTCC conversion with location information")
            << LoggingEvent(test_logger(),
                            Level(Level::DEBUG_INT),
                            QStringLiteral("This is the message"),
                            QStringLiteral("NDC"),
                            properties,
                            QStringLiteral("main"),
                            relative_timestamp,
                            MessageContext("foo.cpp", 100, "foo()"),
                            QStringLiteral("category")
                            )
            << "%r [%t] %p %c %l %x - %m%n"
            << relative_string + " [main] DEBUG Test::TestLog4Qt foo.cpp:100 - foo() NDC - This is the message" + eol
            << 0;
    QTest::newRow("TTCC conversion with file, line and method - Qt logger")
            << LoggingEvent(LogManager::instance()->qtLogger(),
                            Level(Level::DEBUG_INT),
                            QStringLiteral("This is the message"),
                            QStringLiteral("NDC"),
                            properties,
                            QStringLiteral("main"),
                            relative_timestamp,
                            MessageContext("foo.cpp", 100, "foo()"),
                            QStringLiteral("Qt category")
                            )
            << "%r [%t] %p %c %F:%L-%M %x - %m%n"
            << relative_string + " [main] DEBUG Qt category foo.cpp:100-foo() NDC - This is the message" + eol
            << 0;
    QTest::newRow("TTCC conversion with file, line and method - Qt logger no category")
            << LoggingEvent(LogManager::instance()->qtLogger(),
                            Level(Level::DEBUG_INT),
                            QStringLiteral("This is the message"),
                            QStringLiteral("NDC"),
                            properties,
                            QStringLiteral("main"),
                            relative_timestamp,
                            MessageContext("foo.cpp", 100, "foo()"),
                            QString()
                            )
            << "%r [%t] %p %c %F:%L-%M %x - %m%n"
            << relative_string + " [main] DEBUG Qt foo.cpp:100-foo() NDC - This is the message" + eol
            << 0;

    resetLogging();
}

void Log4QtTest::PatternFormatter()
{
    QFETCH(LoggingEvent, event);
    QFETCH(QString, pattern);
    QFETCH(QString, result);
    QFETCH(int, event_count);

    Log4Qt::PatternFormatter pattern_formatter(pattern);
    QCOMPARE(pattern_formatter.format(event), result);

    QCOMPARE(loggingEvents()->list().count(), event_count);
}

void Log4QtTest::Properties_default_data()
{
    mDefaultProperties.clear();
    mDefaultProperties.setProperty(QStringLiteral("X"), QStringLiteral("x"));

    mProperties.clear();
    mProperties.setProperty(QStringLiteral("A"), QStringLiteral("a"));
    mProperties.setProperty(QStringLiteral("B"), QLatin1String(""));
    mProperties.setProperty(QStringLiteral("C"), QString());

    QTest::addColumn<QString>("key");
    QTest::addColumn<QString>("value");

    QTest::newRow("Existing value") << "A" << "a";
    QTest::newRow("Empty value") << "B" << "";
    QTest::newRow("Null value") << "C" << "";
    QTest::newRow("Default value") << "X" << "x";
    QTest::newRow("Non existing value") << "D" << QString();
}

void Log4QtTest::Properties_default()
{
    QFETCH(QString, key);
    QFETCH(QString, value);

    QCOMPARE(mProperties.property(key), value);
}

void Log4QtTest::Properties_names()
{
    mDefaultProperties.clear();
    mDefaultProperties.setProperty(QStringLiteral("X"), QStringLiteral("x"));

    mProperties.clear();
    mProperties.setProperty(QStringLiteral("A"), QStringLiteral("a"));
    mProperties.setProperty(QStringLiteral("B"), QLatin1String(""));

    QStringList property_names = mProperties.propertyNames();
    QCOMPARE(property_names.count(), 3);
    QVERIFY(property_names.contains("A"));
    QVERIFY(property_names.count("B"));
    QVERIFY(property_names.count("X"));
}


void Log4QtTest::Properties_load_device_data()
{
    QTest::addColumn<QByteArray>("buffer");
    QTest::addColumn<int>("property_count");
    QTest::addColumn<QString>("key");
    QTest::addColumn<QString>("value");
    QTest::addColumn<int>("event_count");

    QByteArray buffer;
    QBuffer device(&buffer);
    QTextStream stream(&device);

    buffer.clear();
    device.open(QIODevice::WriteOnly);
    stream << "Truth = Beauty" << endl;
    device.close();
    QTest::newRow("Java class documentation example 1a")
            << buffer << 1 << "Truth" << "Beauty" << 0;

    buffer.clear();
    device.open(QIODevice::WriteOnly);
    stream << "         Truth:Beauty" << endl;
    device.close();
    QTest::newRow("Java class documentation example 1b")
            << buffer << 1 << "Truth" << "Beauty" << 0;

    buffer.clear();
    device.open(QIODevice::WriteOnly);
    stream << "Truth        :Beauty" << endl;
    device.close();
    QTest::newRow("Java class documentation example 1c")
            << buffer << 1 << "Truth" << "Beauty" << 0;

    buffer.clear();
    device.open(QIODevice::WriteOnly);
    stream << "fruits                apple, banana, pear, \\" << endl;
    stream << "                      cantaloupe, watermelon, \\" << endl;
    stream << "                      kiwi, mango" << endl;
    device.close();
    QTest::newRow("Java class documentation example 2")
            << buffer << 1 << "fruits"
            << "apple, banana, pear, cantaloupe, watermelon, kiwi, mango" << 0;

    buffer.clear();
    device.open(QIODevice::WriteOnly);
    stream << "cheese" << endl;
    device.close();
    QTest::newRow("Java class documentation example 3")
            << buffer << 1 << "cheese" << "" << 0;

    buffer.clear();
    device.open(QIODevice::WriteOnly);
    stream << "K\\ e\\=\\:y Value" << endl;
    device.close();
    QTest::newRow("Key escape sequences")
            << buffer << 1 << "K e=:y" << "Value" << 0;

    buffer.clear();
    device.open(QIODevice::WriteOnly);
    stream << "Key V\\t\\n\\ra\\\\l\\\"u\\\'e" << endl;
    device.close();
    QTest::newRow("Value escape sequences")
            << buffer << 1 << "Key" << "V\t\n\ra\\l\"u\'e" << 0;

    buffer.clear();
    device.open(QIODevice::WriteOnly);
    stream << "Key\\t Value\\j" << endl;
    device.close();
    QTest::newRow("Invalid escape sequences")
            << buffer << 1 << "Keyt" << "Valuej" << 2;

    buffer.clear();
    device.open(QIODevice::WriteOnly);
    stream << "Key Valu\\u006fe" << endl;
    device.close();
    QTest::newRow("Unicode escape sequence")
            << buffer << 1 << "Key" << "Valuoe" << 0;

    buffer.clear();
    device.open(QIODevice::WriteOnly);
    stream << "Key Value\\u6f" << endl;
    device.close();
    QTest::newRow("Unicode escape sequence at the end")
            << buffer << 1 << "Key" << "Valueo" << 0;

    buffer.clear();
    device.open(QIODevice::WriteOnly);
    stream << ": Value" << endl;
    device.close();
    QTest::newRow("Empty key")
            << buffer << 1 << "" << "Value" << 1;

    resetLogging();
}


void Log4QtTest::Properties_load_device()
{
    QFETCH(QByteArray, buffer);
    QFETCH(int, property_count);
    QFETCH(QString, key);
    QFETCH(QString, value);
    QFETCH(int, event_count);

    loggingEvents()->clearList();

    QBuffer device(&buffer);
    QTextStream stream(&device);
    device.open(QIODevice::ReadOnly);

    Properties properties;
    properties.load(&device);

    QCOMPARE(properties.count(), property_count);
    QVERIFY(properties.contains(key));
    QCOMPARE(properties.value(key), value);
    QCOMPARE(loggingEvents()->list().count(), event_count);
}


void Log4QtTest::Properties_load_settings()
{
    QSettings settings(mTemporaryDirectory.path()
                       + "/PropetiesLoadSettings.ini", QSettings::IniFormat);
    QBitArray bit_array(5, true);

    settings.setValue(QStringLiteral("A"), "a");
    settings.setValue(QStringLiteral("Group/B"), "b");
    settings.setValue(QStringLiteral("Group/C"), true);
    settings.setValue(QStringLiteral("Group/D"), bit_array);
    settings.setValue(QStringLiteral("Group/Subgroup/E"), "e");

    settings.beginGroup(QStringLiteral("Group"));
    Properties properties;
    properties.load(settings);

    QCOMPARE(properties.count(), 3);
    QVERIFY(properties.contains("B"));
    QCOMPARE(properties.value("B"), QString("b"));
    QVERIFY(properties.contains("C"));
    QCOMPARE(properties.value("C"), QString("true"));
    QVERIFY(properties.contains("D"));
    QCOMPARE(properties.value("D"), QString());
}

void Log4QtTest::OptionConverter_boolean_data()
{
    QTest::addColumn<QString>("value");
    QTest::addColumn<bool>("default_value");
    QTest::addColumn<bool>("result");
    QTest::addColumn<int>("event_count");

    QTest::newRow("true") << "true" << false << true << 0;
    QTest::newRow("enabled") << "enabled" << false << true << 0;
    QTest::newRow("1") << "1" << false << true << 0;
    QTest::newRow("false") << "false" << true << false << 0;
    QTest::newRow("disabled") << "disabled" << true << false << 0;
    QTest::newRow("0") << "0" << true << false << 0;
    QTest::newRow("Case") << "tRuE" << false << true << 0;
    QTest::newRow("Trim") << " true " << false << true << 0;
    QTest::newRow("No Boolean") << "NoBool" << false << false << 1;

    resetLogging();
}


void Log4QtTest::OptionConverter_boolean()
{
    QFETCH(QString, value);
    QFETCH(bool, default_value);
    QFETCH(bool, result);
    QFETCH(int, event_count);

    loggingEvents()->clearList();
    QCOMPARE(OptionConverter::toBoolean(value, default_value), result);
    QCOMPARE(loggingEvents()->list().count(), event_count);
}


void Log4QtTest::OptionConverter_filesize_data()
{
    QTest::addColumn<QString>("value");
    QTest::addColumn<bool>("result_ok");
    QTest::addColumn<qint64>("result");
    QTest::addColumn<int>("event_count");

    QTest::newRow("Int")
            << "135" << true << Q_INT64_C(135) << 0;
    QTest::newRow("Trim")
            << " 135 " << true << Q_INT64_C(135) << 0;
    QTest::newRow("KB")
            << "2KB" << true << Q_INT64_C(2048) << 0;
    QTest::newRow("KB case")
            << "2kb" << true << Q_INT64_C(2048) << 0;
    QTest::newRow("KB trim")
            << " 2KB " << true << Q_INT64_C(2048) << 0;
    QTest::newRow("MB")
            << "100MB" << true << Q_INT64_C(104857600) << 0;
    QTest::newRow("GB")
            << "2GB" << true << Q_INT64_C(2147483648) << 0;
    QTest::newRow("Invalid negative")
            << "-1" << false << Q_INT64_C(0) << 1;
    QTest::newRow("Invalid character")
            << "x" << false << Q_INT64_C(0) << 1;
    QTest::newRow("Invalid character with unit")
            << "xkb" << false << Q_INT64_C(0) << 1;
    QTest::newRow("Invalid additional text")
            << "2KBx" << false << Q_INT64_C(0) << 1;

    resetLogging();
}


void Log4QtTest::OptionConverter_filesize()
{
    QFETCH(QString, value);
    QFETCH(bool, result_ok);
    QFETCH(qint64, result);
    QFETCH(int, event_count);

    loggingEvents()->clearList();
    bool ok;
    QCOMPARE(OptionConverter::toFileSize(value, &ok), result);
    QCOMPARE(ok, result_ok);
    QCOMPARE(loggingEvents()->list().count(), event_count);
}


void Log4QtTest::OptionConverter_int_data()
{
    QTest::addColumn<QString>("value");
    QTest::addColumn<bool>("result_ok");
    QTest::addColumn<int>("result");
    QTest::addColumn<int>("event_count");

    QTest::newRow("Positive") << "12" << true << 12 << 0;
    QTest::newRow("Negative") << "-1" << true << -1 << 0;
    QTest::newRow("Trim") << " 12 " << true << 12 << 0;
    QTest::newRow("No integer") << "12x" << false << 0 << 1;

    resetLogging();
}


void Log4QtTest::OptionConverter_int()
{
    QFETCH(QString, value);
    QFETCH(bool, result_ok);
    QFETCH(int, result);
    QFETCH(int, event_count);

    loggingEvents()->clearList();
    bool ok;
    QCOMPARE(OptionConverter::toInt(value, &ok), result);
    QCOMPARE(ok, result_ok);
    QCOMPARE(loggingEvents()->list().count(), event_count);
}


void Log4QtTest::OptionConverter_level_data()
{
    QTest::addColumn<QString>("value");
    QTest::addColumn<Log4Qt::Level>("default_value");
    QTest::addColumn<Log4Qt::Level>("result");
    QTest::addColumn<int>("event_count");

    QTest::newRow("Case")
            << "WaRn"
            << Log4Qt::Level(Log4Qt::Level::ERROR_INT)
            << Log4Qt::Level(Log4Qt::Level::WARN_INT)
            << 0;
    QTest::newRow("Trim")
            << " warn "
            << Log4Qt::Level(Log4Qt::Level::ERROR_INT)
            << Log4Qt::Level(Log4Qt::Level::WARN_INT)
            << 0;
    QTest::newRow("Default")
            << "NoLevel"
            << Log4Qt::Level(Log4Qt::Level::ERROR_INT)
            << Log4Qt::Level(Log4Qt::Level::ERROR_INT)
            << 2; // One from Level + one from OptionConverter

    resetLogging();
}


void Log4QtTest::OptionConverter_level()
{
    QFETCH(QString, value);
    QFETCH(Log4Qt::Level, default_value);
    QFETCH(Log4Qt::Level, result);
    QFETCH(int, event_count);

    loggingEvents()->clearList();
    QCOMPARE(OptionConverter::toLevel(value, default_value), result);
    QCOMPARE(loggingEvents()->list().count(), event_count);
}


void Log4QtTest::OptionConverter_substitution_data()
{
    mDefaultProperties.clear();
    mProperties.clear();

    QTest::addColumn<QString>("key");
    QTest::addColumn<QString>("value");
    QTest::addColumn<QString>("result");
    QTest::addColumn<int>("event_count");

    QTest::newRow("Existing value")
            << "A" << "a" << "a" << 0;
    QTest::newRow("Existing value")
            << "B" << "b" << "b" << 0;
    QTest::newRow("Empty value")
            << "C" << "" << "" << 0;
    QTest::newRow("Null value")
            << "D" << QString() << "" << 0;
    QTest::newRow("Substitution")
            << "S1" << "begin${A}end" << "beginaend" << 0;
    QTest::newRow("Substitution with two values")
            << "S2" << "begin${A}end${B}" << "beginaendb" << 0;
    QTest::newRow("Substitution recursive")
            << "S3" << "${S1}" << "beginaend" << 0;
    QTest::newRow("Substitution missing bracket")
            << "S4" << "begin${end" << "begin" << 1;
    QTest::newRow("Substitution spare brackets")
            << "S5" << "begin}${A}}end" << "begin}a}end" << 0;
}


void Log4QtTest::OptionConverter_substitution()
{
    QFETCH(QString, key);
    QFETCH(QString, value);
    QFETCH(QString, result);
    QFETCH(int, event_count);

    loggingEvents()->clearList();
    mProperties.setProperty(key, value);
    QCOMPARE(OptionConverter::findAndSubst(mProperties, key), result);
    QCOMPARE(loggingEvents()->list().count(), event_count);
}


void Log4QtTest::OptionConverter_target_data()
{
    QTest::addColumn<QString>("value");
    QTest::addColumn<bool>("result_ok");
    QTest::addColumn<int>("result");
    QTest::addColumn<int>("event_count");

    QTest::newRow("stdout cpp")
            << "STDOUT_TARGET" << true << static_cast<int>(ConsoleAppender::STDOUT_TARGET) << 0;
    QTest::newRow("stdout java")
            << "System.out" << true << static_cast<int>(ConsoleAppender::STDOUT_TARGET) << 0;
    QTest::newRow("stderr cpp")
            << "STDERR_TARGET" << true << static_cast<int>(ConsoleAppender::STDERR_TARGET) << 0;
    QTest::newRow("stderr java")
            << "System.err" << true << static_cast<int>(ConsoleAppender::STDERR_TARGET) << 0;
    QTest::newRow("trim")
            << "  STDOUT_TARGET  " << true << static_cast<int>(ConsoleAppender::STDOUT_TARGET) << 0;
    QTest::newRow("error")
            << "Hello" << false << static_cast<int>(ConsoleAppender::STDOUT_TARGET) << 1;
}


void Log4QtTest::OptionConverter_target()
{
    QFETCH(QString, value);
    QFETCH(bool, result_ok);
    QFETCH(int, result);
    QFETCH(int, event_count);

    loggingEvents()->clearList();
    bool ok;
    QCOMPARE(OptionConverter::toTarget(value, &ok), result);
    QCOMPARE(ok, result_ok);
    QCOMPARE(loggingEvents()->list().count(), event_count);
}



/******************************************************************************
 * Factory requires OptionConverter                                           */


void Log4QtTest::Factory_createAppender_data()
{
    QTest::addColumn<QString>("classname");
    QTest::addColumn<QString>("result");
    QTest::addColumn<int>("event_count");

    QTest::newRow("ConsoleAppender java")
            << "org.apache.log4j.ConsoleAppender" << "Log4Qt::ConsoleAppender" << 0;
    QTest::newRow("ConsoleAppender cpp")
            << "Log4Qt::ConsoleAppender" << "Log4Qt::ConsoleAppender" << 0;
    QTest::newRow("DailyRollingFileAppender java")
            << "org.apache.log4j.DailyRollingFileAppender" << "Log4Qt::DailyRollingFileAppender" << 0;
    QTest::newRow("DailyRollingFileAppender cpp")
            << "Log4Qt::DailyRollingFileAppender" << "Log4Qt::DailyRollingFileAppender" << 0;
    QTest::newRow("DebugAppender java")
            << "org.apache.log4j.varia.DebugAppender" << "Log4Qt::DebugAppender" << 0;
    QTest::newRow("DebugAppender cpp")
            << "Log4Qt::DebugAppender" << "Log4Qt::DebugAppender" << 0;
    QTest::newRow("FileAppender java")
            << "org.apache.log4j.FileAppender" << "Log4Qt::FileAppender" << 0;
    QTest::newRow("FileAppender cpp")
            << "Log4Qt::FileAppender" << "Log4Qt::FileAppender" << 0;
    QTest::newRow("ListAppender java")
            << "org.apache.log4j.varia.ListAppender" << "Log4Qt::ListAppender" << 0;
    QTest::newRow("ListAppender cpp")
            << "Log4Qt::ListAppender" << "Log4Qt::ListAppender" << 0;
    QTest::newRow("NullAppender java")
            << "org.apache.log4j.varia.NullAppender" << "Log4Qt::NullAppender" << 0;
    QTest::newRow("NullAppender cpp")
            << "Log4Qt::NullAppender" << "Log4Qt::NullAppender" << 0;
    QTest::newRow("RollingFileAppender java")
            << "org.apache.log4j.RollingFileAppender" << "Log4Qt::RollingFileAppender" << 0;
    QTest::newRow("RollingFileAppender cpp")
            << "Log4Qt::RollingFileAppender" << "Log4Qt::RollingFileAppender" << 0;
}


void Log4QtTest::Factory_createAppender()
{
    QFETCH(QString, classname);
    QFETCH(QString, result);
    QFETCH(int, event_count);

    loggingEvents()->clearList();
    QObject *p_object = Factory::createAppender(classname);
    QVERIFY(p_object != nullptr);
    QCOMPARE(QString::fromLatin1(p_object->metaObject()->className()), result);
    delete p_object;
    QCOMPARE(loggingEvents()->list().count(), event_count);
}


void Log4QtTest::Factory_createFilter_data()
{
    QTest::addColumn<QString>("classname");
    QTest::addColumn<QString>("result");
    QTest::addColumn<int>("event_count");

    QTest::newRow("DenyAllFilter java")
            << "org.apache.log4j.varia.DenyAllFilter" << "Log4Qt::DenyAllFilter" << 0;
    QTest::newRow("DenyAllFilter cpp")
            << "Log4Qt::DenyAllFilter" << "Log4Qt::DenyAllFilter" << 0;
    QTest::newRow("LevelMatchFilter java")
            << "org.apache.log4j.varia.LevelMatchFilter" << "Log4Qt::LevelMatchFilter" << 0;
    QTest::newRow("LevelMatchFilter cpp")
            << "Log4Qt::LevelMatchFilter" << "Log4Qt::LevelMatchFilter" << 0;
    QTest::newRow("LevelRangeFilter java")
            << "org.apache.log4j.varia.LevelRangeFilter" << "Log4Qt::LevelRangeFilter" << 0;
    QTest::newRow("LevelRangeFilter cpp")
            << "Log4Qt::LevelRangeFilter" << "Log4Qt::LevelRangeFilter" << 0;
    QTest::newRow("StringMatchFilter java")
            << "org.apache.log4j.varia.StringMatchFilter" << "Log4Qt::StringMatchFilter" << 0;
    QTest::newRow("StringMatchFilter cpp")
            << "Log4Qt::StringMatchFilter" << "Log4Qt::StringMatchFilter" << 0;
}


void Log4QtTest::Factory_createFilter()
{
    QFETCH(QString, classname);
    QFETCH(QString, result);
    QFETCH(int, event_count);

    loggingEvents()->clearList();
    QObject *p_object = Factory::createFilter(classname);
    QVERIFY(p_object != nullptr);
    QCOMPARE(QString::fromLatin1(p_object->metaObject()->className()), result);
    delete p_object;
    QCOMPARE(loggingEvents()->list().count(), event_count);
}


void Log4QtTest::Factory_createLayout_data()
{
    QTest::addColumn<QString>("classname");
    QTest::addColumn<QString>("result");
    QTest::addColumn<int>("event_count");

    QTest::newRow("PatternLayout java")
            << "org.apache.log4j.PatternLayout" << "Log4Qt::PatternLayout" << 0;
    QTest::newRow("PatternLayout cpp")
            << "Log4Qt::PatternLayout" << "Log4Qt::PatternLayout" << 0;
    QTest::newRow("SimpleLayout java")
            << "org.apache.log4j.SimpleLayout" << "Log4Qt::SimpleLayout" << 0;
    QTest::newRow("SimpleLayout cpp")
            << "Log4Qt::SimpleLayout" << "Log4Qt::SimpleLayout" << 0;
    QTest::newRow("TTCCLayout java")
            << "org.apache.log4j.TTCCLayout" << "Log4Qt::TTCCLayout" << 0;
    QTest::newRow("TTCCLayout cpp")
            << "Log4Qt::TTCCLayout" << "Log4Qt::TTCCLayout" << 0;
}


void Log4QtTest::Factory_createLayout()
{
    QFETCH(QString, classname);
    QFETCH(QString, result);
    QFETCH(int, event_count);

    loggingEvents()->clearList();
    QObject *p_object = Factory::createLayout(classname);
    QVERIFY(p_object != nullptr);
    QCOMPARE(QString::fromLatin1(p_object->metaObject()->className()), result);
    delete p_object;
    QCOMPARE(loggingEvents()->list().count(), event_count);
}


void Log4QtTest::Factory_setObjectProperty_data()
{
    QTest::addColumn<QString>("appenderclass");
    QTest::addColumn<QString>("property");
    QTest::addColumn<QString>("value");
    QTest::addColumn<QString>("result_value");
    QTest::addColumn<int>("event_count");

    QTest::newRow("Bool")
            << "Log4Qt::FileAppender"
            << "immediateFlush" << "false"
            << "false" << 0;
    QTest::newRow("Int")
            << "Log4Qt::ListAppender"
            << "maxCount" << "10"
            << "10" << 0;
    QTest::newRow("QString")
            << "Log4Qt::FileAppender"
            << "file" << "C:\\tmp\\mylog.txt"
            << "C:\\tmp\\mylog.txt" << 0;
    QTest::newRow("Null object")
            << "Log4Qt::NullAppender"
            << "maxCount" << "10"
            << "" << 1;
    QTest::newRow("Empty property string")
            << "Log4Qt::NullAppender"
            << "" << "10"
            << "" << 1;
    QTest::newRow("Property does not exist")
            << "Log4Qt::NullAppender"
            << "Colour" << "10"
            << "" << 1;
    QTest::newRow("Property not writable")
            << "Log4Qt::NullAppender"
            << "isClosed" << "10"
            << "" << 1;
    QTest::newRow("Property of wrong type")
            << "Log4Qt::RollingFileAppender"
            << "maximumFileSize" << "7"
            << "" << 1;
}


void Log4QtTest::Factory_setObjectProperty()
{
    QFETCH(QString, appenderclass);
    QFETCH(QString, property);
    QFETCH(QString, value);
    QFETCH(QString, result_value);
    QFETCH(int, event_count);

    loggingEvents()->clearList();
    QObject *p_object = Factory::createAppender(appenderclass);

    Factory::setObjectProperty(p_object, property, value);
    QCOMPARE(loggingEvents()->list().count(), event_count);
    if (loggingEvents()->list().count() == 0)
        QCOMPARE(p_object->property(property.toLatin1()).toString(), result_value);

    delete p_object;
}



/******************************************************************************
 * varia                                                               */


void Log4QtTest::ListAppender()
{
    Log4Qt::ListAppender appender;

    // Store messages
    QCOMPARE(appender.list().count(), 0);
    appender.doAppend(LoggingEvent(test_logger(), Level::WARN_INT, QStringLiteral("Message1")));
    appender.doAppend(LoggingEvent(test_logger(), Level::WARN_INT, QStringLiteral("Message2")));
    appender.doAppend(LoggingEvent(test_logger(), Level::WARN_INT, QStringLiteral("Message3")));
    QCOMPARE(appender.list().count(), 3);

    // Delete oldest, if max is set
    appender.setMaxCount(2);
    QCOMPARE(appender.list().count(), 2);
    QCOMPARE(appender.list().at(0).message(), QString("Message2"));
    QCOMPARE(appender.list().at(1).message(), QString("Message3"));

    // Ignore new ones added
    appender.doAppend(LoggingEvent(test_logger(), Level::WARN_INT, QStringLiteral("Message4")));
    QCOMPARE(appender.list().count(), 2);
    QCOMPARE(appender.list().at(0).message(), QString("Message2"));
    QCOMPARE(appender.list().at(1).message(), QString("Message3"));

    // Clear
    appender.clearList();
    QCOMPARE(appender.list().count(), 0);
}


void Log4QtTest::DenyAllFilter()
{
    Log4Qt::DenyAllFilter filter;
    LoggingEvent event(test_logger(), Level::WARN_INT, QStringLiteral("Message"));
    QCOMPARE(filter.decide(event), Filter::DENY);
}


void Log4QtTest::LevelMatchFilter_data()
{
    QTest::addColumn<QString>("filter_level");
    QTest::addColumn<bool>("accept_on_match");
    QTest::addColumn<QString>("event_level");
    QTest::addColumn<QString>("result");

    QTest::newRow("No match, No accept") << "WARN" << false << "TRACE" << "NEUTRAL";
    QTest::newRow("No match, Accept") << "WARN" << true << "TRACE" << "NEUTRAL";
    QTest::newRow("Match, No accept") << "WARN" << false << "WARN" << "DENY";
    QTest::newRow("Match, Accept") << "WARN" << true << "WARN" << "ACCEPT";
}


void Log4QtTest::LevelMatchFilter()
{
    QFETCH(QString, filter_level);
    QFETCH(bool, accept_on_match);
    QFETCH(QString, event_level);
    QFETCH(QString, result);

    Log4Qt::LevelMatchFilter filter;
    filter.setLevelToMatch(Level::fromString(filter_level));
    filter.setAcceptOnMatch(accept_on_match);
    LoggingEvent
    event(test_logger(), Level::fromString(event_level),
          QStringLiteral("Message"));

    QString decision =
        enumValueToKey(&filter, "Decision", filter.decide(event));
    QCOMPARE(decision, result);
}


void Log4QtTest::LevelRangeFilter_data()
{
    QTest::addColumn<QString>("filter_min_level");
    QTest::addColumn<QString>("filter_max_level");
    QTest::addColumn<bool>("accept_on_match");
    QTest::addColumn<QString>("event_level");
    QTest::addColumn<QString>("result");

    QTest::newRow("Too low, No accept") << "DEBUG" << "ERROR" << false << "TRACE" << "DENY";
    QTest::newRow("Too high, No accept") << "DEBUG" << "ERROR" << false << "FATAL" << "DENY";
    QTest::newRow("Min, No accept") << "DEBUG" << "ERROR" << false << "DEBUG" << "NEUTRAL";
    QTest::newRow("Inside, No accept") << "DEBUG" << "ERROR" << false << "WARN" << "NEUTRAL";
    QTest::newRow("Max, No accept") << "DEBUG" << "ERROR" << false << "ERROR" << "NEUTRAL";
    QTest::newRow("Min not initialised, No accept") << "" << "ERROR" << false << "TRACE" << "NEUTRAL";
    QTest::newRow("Max not initialised, No accept") << "DEBUG" << "" << false << "FATAL" << "NEUTRAL";
    QTest::newRow("Too low, Accept") << "DEBUG" << "ERROR" << true << "TRACE" << "DENY";
    QTest::newRow("Too high, Accept") << "DEBUG" << "ERROR" << true << "FATAL" << "DENY";
    QTest::newRow("Min, Accept") << "DEBUG" << "ERROR" << true << "DEBUG" << "ACCEPT";
    QTest::newRow("Inside, Accept") << "DEBUG" << "ERROR" << true << "WARN" << "ACCEPT";
    QTest::newRow("Max, Accept") << "DEBUG" << "ERROR" << true << "ERROR" << "ACCEPT";
    QTest::newRow("Min not initialised, Accept") << "" << "ERROR" << true << "TRACE" << "ACCEPT";
    QTest::newRow("Max not initialised, Accept") << "DEBUG" << "" << true << "FATAL" << "ACCEPT";
}


void Log4QtTest::LevelRangeFilter()
{
    QFETCH(QString, filter_min_level);
    QFETCH(QString, filter_max_level);
    QFETCH(bool, accept_on_match);
    QFETCH(QString, event_level);
    QFETCH(QString, result);

    Log4Qt::LevelRangeFilter filter;
    if (!filter_min_level.isEmpty())
        filter.setLevelMin(Level::fromString(filter_min_level));
    if (!filter_max_level.isEmpty())
        filter.setLevelMax(Level::fromString(filter_max_level));
    filter.setAcceptOnMatch(accept_on_match);
    LoggingEvent
    event(test_logger(), Level::fromString(event_level),
          QStringLiteral("Message"));

    QString decision =
        enumValueToKey(&filter, "Decision", filter.decide(event));
    QCOMPARE(decision, result);
}


void Log4QtTest::StringMatchFilter_data()
{
    QTest::addColumn<QString>("filter_string");
    QTest::addColumn<bool>("accept_on_match");
    QTest::addColumn<QString>("event_string");
    QTest::addColumn<QString>("result");

    QTest::newRow("No match, No accept") << "MESSAGE" << false << "This is a message" << "NEUTRAL";
    QTest::newRow("Match, No accept") << "This" << false << "This is a message" << "DENY";
    QTest::newRow("No match, Accept") << "MESSAGE" << true << "This is a message" << "NEUTRAL";
    QTest::newRow("Match, Accept") << "This" << true << "This is a message" << "ACCEPT";
    QTest::newRow("Empty message, No accept") << "This" << false << "" << "NEUTRAL";
    QTest::newRow("Empty message, Accept") << "This" << true << "" << "NEUTRAL";
    QTest::newRow("Empty filter, No accept") << "" << false << "This is a message" << "NEUTRAL";
    QTest::newRow("Empty filter, Accept") << "" << true << "This is a message" << "NEUTRAL";
}


void Log4QtTest::StringMatchFilter()
{
    QFETCH(QString, filter_string);
    QFETCH(bool, accept_on_match);
    QFETCH(QString, event_string);
    QFETCH(QString, result);

    Log4Qt::StringMatchFilter filter;
    filter.setStringToMatch(filter_string);
    filter.setAcceptOnMatch(accept_on_match);
    LoggingEvent event(test_logger(), Level::WARN_INT, event_string);

    QString decision =
        enumValueToKey(&filter, "Decision", filter.decide(event));
    QCOMPARE(decision, result);
}



/******************************************************************************
 * log4qt                                                                     */


void Log4QtTest::AppenderSkeleton_threshold()
{
    resetLogging();

    auto *p_appender = new Log4Qt::ListAppender();
    Log4Qt::Logger *p_logger = test_logger();
    p_logger->addAppender(p_appender);

    // Threshold
    p_appender->setThreshold(Level::ERROR_INT);
    p_appender->doAppend(LoggingEvent(p_logger, Level::WARN_INT, QStringLiteral("Warn")));
    p_appender->doAppend(LoggingEvent(p_logger, Level::ERROR_INT, QStringLiteral("Error")));
    p_appender->doAppend(LoggingEvent(p_logger, Level::FATAL_INT, QStringLiteral("Fatal")));
    QCOMPARE(p_appender->list().count(), 2);
    QCOMPARE(p_appender->list().at(0).level(), Level(Level::ERROR_INT));
    QCOMPARE(p_appender->list().at(1).level(), Level(Level::FATAL_INT));
}


void Log4QtTest::AppenderSkeleton_filter_data()
{
    resetLogging();

    QTest::addColumn<QString>("filter1_level");
    QTest::addColumn<bool>("filter1_accept");
    QTest::addColumn<QString>("filter2_level");
    QTest::addColumn<bool>("filter2_accept");
    QTest::addColumn<QString>("event_level");
    QTest::addColumn<int>("event_count");

    QTest::newRow("Single filter, NEUTRAL")
            << "WARN" << true << "" << true << "TRACE" << 1;
    QTest::newRow("Single filter, ACCEPT")
            << "WARN" << true << "" << true << "WARN" << 1;
    QTest::newRow("Single filter, DENY")
            << "WARN" << false << "" << true << "WARN" << 0;

    QTest::newRow("Double filter, NEUTRAL NEUTRAL")
            << "WARN" << true << "WARN" << true << "TRACE" << 1;
    QTest::newRow("Double filter, NEUTRAL ACCEPT")
            << "WARN" << true << "TRACE" << true << "TRACE" << 1;
    QTest::newRow("Double filter, NEUTRAL DENY")
            << "WARN" << true << "TRACE" << false << "TRACE" << 0;

    QTest::newRow("Double filter, ACCEPT NEUTRAL")
            << "WARN" << true << "TRACE" << true << "WARN" << 1;
    QTest::newRow("Double filter, ACCEPT ACCEPT")
            << "WARN" << true << "WARN" << true << "WARN" << 1;
    QTest::newRow("Double filter, ACCEPT DENY")
            << "WARN" << true << "WARN" << false << "WARN" << 1;

    QTest::newRow("Double filter, DENY NEUTRAL")
            << "WARN" << false << "TRACE" << true << "WARN" << 0;
    QTest::newRow("Double filter, DENY ACCEPT")
            << "WARN" << false << "WARN" << true << "WARN" << 0;
    QTest::newRow("Double filter, DENY DENY")
            << "WARN" << false << "WARN" << false << "WARN" << 0;
}


void Log4QtTest::AppenderSkeleton_filter()
{
    QFETCH(QString, filter1_level);
    QFETCH(bool, filter1_accept);
    QFETCH(QString, filter2_level);
    QFETCH(bool, filter2_accept);
    QFETCH(QString, event_level);
    QFETCH(int, event_count);

    Log4Qt::ListAppender appender;

    if (!filter1_level.isEmpty())
    {
        auto *filter = new Log4Qt::LevelMatchFilter();
        filter->setLevelToMatch(Level::fromString(filter1_level));
        filter->setAcceptOnMatch(filter1_accept);
        appender.addFilter(FilterSharedPtr(filter));
    }
    if (!filter2_level.isEmpty())
    {
        auto *filter = new Log4Qt::LevelMatchFilter();
        filter->setLevelToMatch(Level::fromString(filter2_level));
        filter->setAcceptOnMatch(filter2_accept);
        appender.addFilter(FilterSharedPtr(filter));
    }

    appender.doAppend(LoggingEvent(test_logger(),
                                   Level::fromString(event_level), QStringLiteral("Message")));
    QCOMPARE(appender.list().count(), event_count);
}


void Log4QtTest::BasicConfigurator()
{
    LogManager::resetConfiguration();
    resetLogging();
    QVERIFY(Log4Qt::BasicConfigurator::configure());

    Logger *p_logger = LogManager::rootLogger();
    QCOMPARE(p_logger->appenders().count(), 1);
    auto *p_appender =
        qobject_cast<ConsoleAppender *>(p_logger->appenders().at(0).data());
    QCOMPARE(p_appender != nullptr, true);
    QVERIFY(p_appender->isActive());
    QVERIFY(!p_appender->isClosed());
    QCOMPARE(p_appender->target(), QString::fromLatin1("STDOUT_TARGET"));
    auto *p_layout =
        qobject_cast<PatternLayout *>(p_appender->layout().data());
    QVERIFY(p_layout != nullptr);
    QCOMPARE(p_layout->conversionPattern(), QString("%r [%t] %p %c %x - %m%n"));

    Log4Qt::Logger *logger = LogManager::rootLogger();
    logger->trace(QStringLiteral("Trace message")); //Disabled by default
    logger->debug(QStringLiteral("Debug message"));
    logger->info(QStringLiteral("Info message"));
    logger->warn(QStringLiteral("Warn message"));
    logger->error(QStringLiteral("Error message"));
    logger->fatal(QStringLiteral("Fatal message"));
}


void Log4QtTest::FileAppender()
{
    resetLogging();

    QString dir(mTemporaryDirectory.path() + "/FileAppender");
    QString file(QStringLiteral("/log"));

    Log4Qt::FileAppender appender1(LayoutSharedPtr(new SimpleLayout()), dir + file, false);
    appender1.setName(QStringLiteral("Fileappender1"));
    appender1.activateOptions();
    appender1.doAppend(LoggingEvent(test_logger(), Level::DEBUG_INT,
                                    QStringLiteral("Message 0")));
    appender1.doAppend(LoggingEvent(test_logger(), Level::DEBUG_INT,
                                    QStringLiteral("Message 1")));
    appender1.close();
    QStringList expected;
    QString result;
    expected << QStringLiteral("log");
    if (!validateDirContents(dir, expected, result))
        QFAIL(qPrintable(result));
    expected.clear();
    expected << QStringLiteral("DEBUG - Message 0") << QStringLiteral("DEBUG - Message 1");
    if (!validateFileContents(dir + file, expected, result))
        QFAIL(qPrintable(result));

    Log4Qt::FileAppender appender2(LayoutSharedPtr(new SimpleLayout()), dir + file, false);
    appender2.setName(QStringLiteral("Fileappender2"));
    appender2.activateOptions();
    appender2.doAppend(LoggingEvent(test_logger(), Level::DEBUG_INT,
                                    QStringLiteral("Message 2")));
    appender2.doAppend(LoggingEvent(test_logger(), Level::DEBUG_INT,
                                    QStringLiteral("Message 3")));
    appender2.close();
    expected.clear();
    expected << QStringLiteral("log");
    if (!validateDirContents(dir, expected, result))
        QFAIL(qPrintable(result));
    expected.clear();
    expected << QStringLiteral("DEBUG - Message 2") << QStringLiteral("DEBUG - Message 3");
    if (!validateFileContents(dir + file, expected, result))
        QFAIL(qPrintable(result));

    Log4Qt::FileAppender appender3(LayoutSharedPtr(new SimpleLayout()), dir + file, true);
    appender3.setName(QStringLiteral("Fileappender3"));
    appender3.activateOptions();
    appender3.doAppend(LoggingEvent(test_logger(), Level::DEBUG_INT,
                                    QStringLiteral("Message 4")));
    appender3.doAppend(LoggingEvent(test_logger(), Level::DEBUG_INT,
                                    QStringLiteral("Message 5")));
    appender3.close();
    expected.clear();
    expected << QStringLiteral("log");
    if (!validateDirContents(dir, expected, result))
        QFAIL(qPrintable(result));
    expected.clear();
    expected
            << QStringLiteral("DEBUG - Message 2") << QStringLiteral("DEBUG - Message 3")
            << QStringLiteral("DEBUG - Message 4") << QStringLiteral("DEBUG - Message 5");
    if (!validateFileContents(dir + file, expected, result))
        QFAIL(qPrintable(result));

    QCOMPARE(loggingEvents()->list().count(), 0);
}


void Log4QtTest::DailyRollingFileAppender()
{
    resetLogging();

    if (mSkipLongTests)
        QSKIP("Skipping long running test", SkipSingle);
    qDebug() << "The test is time based and takes approximately 3 minutes ...";

    QString dir(mTemporaryDirectory.path() + "/DailyRollingFileAppender");
    QString file(QStringLiteral("/log"));

    // Using a RollingFileAppender with 2 files history and 3 messages per file
    Log4Qt::DailyRollingFileAppender appender;
    appender.setName(QStringLiteral("DailyRollingFileAppender"));
    appender.setFile(dir + file);
    appender.setLayout(LayoutSharedPtr(new SimpleLayout()));
    appender.setDatePattern(DailyRollingFileAppender::MINUTELY_ROLLOVER);

    // Start on a full minute
    QDateTime now = QDateTime::currentDateTime();
    QTest::qSleep((60 - now.time().second()) * 1000);
    appender.activateOptions();

    qDebug() << "   1 / 7";
    appender.doAppend(LoggingEvent(test_logger(), Level::DEBUG_INT,
                                   QStringLiteral("Message 0")));
    int i;
    for (i = 1; i < 7; i++)
    {
        QTest::qSleep(21 * 1000);
        qDebug() << "  " << i + 1 << "/" << 7;
        appender.doAppend(LoggingEvent(test_logger(), Level::DEBUG_INT,
                                       QStringLiteral("Message %1").arg(i)));
    }

    QCOMPARE(loggingEvents()->list().count(), 0);

    // Validate directory
    QStringList expected;
    QString result;
    expected << QStringLiteral("log") << "log"
             + dailyRollingFileAppenderSuffix(now.addSecs(60)) << "log"
             + dailyRollingFileAppenderSuffix(now.addSecs(120));
    if (!validateDirContents(dir, expected, result))
        QFAIL(qPrintable(result));

    // Validate files
    expected.clear();
    expected << QStringLiteral("DEBUG - Message 6");
    if (!validateFileContents(dir + file, expected, result))
        QFAIL(qPrintable(result));
    expected.clear();
    expected << QStringLiteral("DEBUG - Message 0") << QStringLiteral("DEBUG - Message 1")
             << QStringLiteral("DEBUG - Message 2");
    if (!validateFileContents(dir + file
                              + dailyRollingFileAppenderSuffix(now.addSecs(60)),
                              expected, result))
        QFAIL(qPrintable(result));
    expected.clear();
    expected << QStringLiteral("DEBUG - Message 3") << QStringLiteral("DEBUG - Message 4")
             << QStringLiteral("DEBUG - Message 5");
    if (!validateFileContents(dir + file
                              + dailyRollingFileAppenderSuffix(now.addSecs(120)),
                              expected, result))
        QFAIL(qPrintable(result));
}

void Log4QtTest::LoggingEvent_stream_data()
{
    QTest::addColumn<LoggingEvent>("original");

    qint64 timestamp =
        DateTime(QDateTime(QDate(2001, 9, 7), QTime(15, 7, 5, 9))).toMSecsSinceEpoch();
    QHash<QString, QString> properties;
    properties.insert(QStringLiteral("A"), QStringLiteral("a"));
    properties.insert(QStringLiteral("B"), QStringLiteral("b"));
    properties.insert(QStringLiteral("C"), QStringLiteral("c"));

    QTest::newRow("Empty logging event")
            << LoggingEvent();
    QTest::newRow("Logging event")
            << LoggingEvent(test_logger(),
                            Level(Level::WARN_INT),
                            QStringLiteral("This is a message"),
                            QStringLiteral("NDC"),
                            properties,
                            QStringLiteral("main"),
                            timestamp);
    QTest::newRow("Logging no logger")
            << LoggingEvent(nullptr,
                            Level(Level::WARN_INT),
                            QStringLiteral("This is a message"),
                            QStringLiteral("NDC"),
                            properties,
                            QStringLiteral("main"),
                            timestamp);

    resetLogging();
}


void Log4QtTest::LoggingEvent_stream()
{
    QFETCH(LoggingEvent, original);

    QByteArray array;
    QBuffer buffer(&array);
    buffer.open(QIODevice::WriteOnly);
    QDataStream stream(&buffer);
    stream << original;
    buffer.close();

    buffer.open(QIODevice::ReadOnly);
    LoggingEvent streamed;
    stream >> streamed;
    buffer.close();

    QCOMPARE(original.level(), streamed.level());
    QCOMPARE(original.loggename(), streamed.loggename());
    QCOMPARE(original.message(), streamed.message());
    QCOMPARE(original.ndc(), streamed.ndc());
    QCOMPARE(original.properties().count(), streamed.properties().count());
    const auto keys = original.properties().keys();
    for (const auto &key : keys)
    {
        QVERIFY(streamed.properties().contains(key));
        QCOMPARE(original.properties().value(key),
                 streamed.properties().value(key));
    }
    QCOMPARE(original.sequenceNumber(), streamed.sequenceNumber());
    QCOMPARE(original.threadName(), streamed.threadName());
    QCOMPARE(original.timeStamp(), streamed.timeStamp());

    QCOMPARE(loggingEvents()->list().count(), 0);
}

void Log4QtTest::LogManager_configureLogLogger()
{
    resetLogging();
    LogManager::logLogger()->removeAppender(mpLoggingEvents);

    LogManager::resetConfiguration();
    Log4Qt::Logger *p_logger = LogManager::logLogger();
    QCOMPARE(p_logger->appenders().count(), 2);
    ConsoleAppender *p_appender;
    TTCCLayout *p_layout;

    p_appender = qobject_cast<ConsoleAppender *>(p_logger->appenders().at(0).data());
    QCOMPARE(p_appender != nullptr, true);
    QVERIFY(p_appender->isActive());
    QVERIFY(!p_appender->isClosed());
    QCOMPARE(p_appender->target(), QString::fromLatin1("STDOUT_TARGET"));
    p_layout = qobject_cast<TTCCLayout *>(p_appender->layout().data());
    QVERIFY(p_layout != nullptr);

    p_appender = qobject_cast<ConsoleAppender *>(p_logger->appenders().at(1).data());
    QCOMPARE(p_appender != nullptr, true);
    QVERIFY(p_appender->isActive());
    QVERIFY(!p_appender->isClosed());
    QCOMPARE(p_appender->target(), QString::fromLatin1("STDERR_TARGET"));
    p_layout = qobject_cast<TTCCLayout *>(p_appender->layout().data());
    QVERIFY(p_layout != nullptr);

}


void Log4QtTest::PropertyConfigurator_missing_appender()
{
    LogManager::resetConfiguration();
    resetLogging();
    mDefaultProperties.clear();
    mProperties.clear();

    mProperties.setProperty(QStringLiteral("log4j.logger.MissingAppender"),
                            QStringLiteral("INHERITED, A"));
    QVERIFY(!PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 1);
    QCOMPARE(loggingEvents()->list().count(), 1);
}


void Log4QtTest::PropertyConfigurator_unknown_appender_class()
{
    LogManager::resetConfiguration();
    resetLogging();
    mDefaultProperties.clear();
    mProperties.clear();

    mProperties.setProperty(QStringLiteral("log4j.logger.UnknownAppender"),
                            QStringLiteral("INHERITED, A"));
    mProperties.setProperty(QStringLiteral("log4j.appender.A"),
                            QStringLiteral("org.apache.log4j.UnknownAppender"));
    QVERIFY(!PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 1);
    QCOMPARE(loggingEvents()->list().count(), 2); // Warning from Factory, Error PropertyConfigurator
}


void Log4QtTest::PropertyConfigurator_missing_layout()
{
    resetLogging();
    mDefaultProperties.clear();
    mProperties.clear();

    mProperties.setProperty(QStringLiteral("log4j.logger.MissingLayout"),
                            QStringLiteral("INHERITED, A"));
    mProperties.setProperty(QStringLiteral("log4j.appender.A"),
                            QStringLiteral("org.apache.log4j.ConsoleAppender"));
    QVERIFY(!PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 1);
    QCOMPARE(loggingEvents()->list().count(), 1);
}


void Log4QtTest::PropertyConfigurator_unknown_layout_class()
{
    resetLogging();
    mDefaultProperties.clear();
    mProperties.clear();

    mProperties.setProperty(QStringLiteral("log4j.logger.UnknownLayout"),
                            QStringLiteral("INHERITED, A"));
    mProperties.setProperty(QStringLiteral("log4j.appender.A"),
                            QStringLiteral("org.apache.log4j.ConsoleAppender"));
    mProperties.setProperty(QStringLiteral("log4j.appender.A.layout"),
                            QStringLiteral("org.apache.log4j.UnknownLayout"));
    QVERIFY(!PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 1);
    QCOMPARE(loggingEvents()->list().count(), 2); // Warning from Factory, Error PropertyConfigurator
}


void Log4QtTest::PropertyConfigurator_reset()
{
    resetLogging();
    mDefaultProperties.clear();
    mProperties.clear();

    // - Create a logger with an appender
    // - If the reset flag is not set, configure must leave the appender
    // - If the reset flag is set to an invalid value, configure must raise an
    //   error and leave the appender
    // - If the reset flag is set, configure must remove the appender

    const QLatin1String key_reset("log4j.reset");
    test_logger()->addAppender(new Log4Qt::ListAppender);
    mProperties.setProperty(key_reset,
                            QStringLiteral("false"));
    QVERIFY(PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 0);
    QCOMPARE(loggingEvents()->list().count(), 0);
    QCOMPARE(test_logger()->appenders().count(), 1);

    mProperties.setProperty(key_reset,
                            QStringLiteral("No boolean"));
    QVERIFY(!PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 1);
    QCOMPARE(loggingEvents()->list().count(), 1);
    QCOMPARE(test_logger()->appenders().count(), 1);

    resetLogging();
    mProperties.setProperty(key_reset,
                            QStringLiteral("true"));
    QVERIFY(PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 0);
    QCOMPARE(loggingEvents()->list().count(), 0);
    QCOMPARE(test_logger()->appenders().count(), 0);
}


void Log4QtTest::PropertyConfigurator_debug()
{
    resetLogging();
    mDefaultProperties.clear();
    mProperties.clear();

    // - Set the log logger level to INFO
    // - If debug is not set, configure must leave the log logger level
    //   unaltered
    // - If debug is set, but with no valid level string, configure must set
    //   the log logger level to DEBUG
    // - If debug is set to the level TRACE, configure must set the log logger
    //   level to TRACE

    const QLatin1String key_debug("log4j.Debug");
    Logger *p_logger = LogManager::logLogger();
    p_logger->setLevel(Level::INFO_INT);

    QVERIFY(PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 0);
    QCOMPARE(loggingEvents()->list().count(), 0);
    QCOMPARE(p_logger->level(), Level(Level::INFO_INT));

    mProperties.setProperty(key_debug,
                            QStringLiteral("true"));
    QVERIFY(PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 0);
    // QCOMPARE(loggingEvents()->list().count(), 1); // Warning from Level::fromString() & several debug messages
    QCOMPARE(p_logger->level(), Level(Level::DEBUG_INT));

    loggingEvents()->clearList();
    mProperties.setProperty(key_debug,
                            QStringLiteral("TRACE"));
    QVERIFY(PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 0);
    // QCOMPARE(loggingEvents()->list().count(), 1); // Warning from PropertyConfigurator & several debug/trace messages
    QCOMPARE(p_logger->level(), Level(Level::TRACE_INT));
}


void Log4QtTest::PropertyConfigurator_threshold()
{
    resetLogging();
    mDefaultProperties.clear();
    mProperties.clear();

    // - Set the repository threshold to INFO
    // - If the threshold is not set, configure must leave the repository
    //   threshold unaltered
    // - If the threshold is set to an invalid value, configure must raise an
    //   error and set the threshold to ALL
    // - If the threshold is set to WARN, configure must set the repository
    //   threshold to WARN

    const QLatin1String key_threshold("log4j.threshold");
    LogManager::loggerRepository()->setThreshold(Level::INFO_INT);

    QVERIFY(PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 0);
    QCOMPARE(loggingEvents()->list().count(), 0);
    QCOMPARE(LogManager::loggerRepository()->threshold(), Level(Level::INFO_INT));

    mProperties.setProperty(key_threshold,
                            QStringLiteral("Not a value"));
    QVERIFY(!PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 1);
    QCOMPARE(loggingEvents()->list().count(), 2); // Warning by Level, Error from OptionConverter
    QCOMPARE(LogManager::loggerRepository()->threshold(), Level(Level::ALL_INT));

    loggingEvents()->clearList();
    mProperties.setProperty(key_threshold,
                            QStringLiteral("WARN"));
    QVERIFY(PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 0);
    QCOMPARE(loggingEvents()->list().count(), 0);
    QCOMPARE(LogManager::loggerRepository()->threshold(), Level(Level::WARN_INT));
}


void Log4QtTest::PropertyConfigurator_handleQtMessages()
{
    resetLogging();
    mDefaultProperties.clear();
    mProperties.clear();

    // - Set handle Qt messages to true
    // - If handle Qt messages is not set, configure must leave handle Qt
    //   messages unaltered
    // - If handle Qt messages is set to an invalid value, configure must raise
    //   an error and set handle Qt messages to false
    // - If handle Qt messages is true, configure must set handle Qt messages
    //   to true

    const QLatin1String key_handle_qt_messages("log4j.handleQtMessages");
    LogManager::setHandleQtMessages(true);

    QVERIFY(PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 0);
    QCOMPARE(loggingEvents()->list().count(), 0);
    QCOMPARE(LogManager::handleQtMessages(), true);

    mProperties.setProperty(key_handle_qt_messages,
                            QStringLiteral("No boolean"));
    QVERIFY(!PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 1);
    QCOMPARE(loggingEvents()->list().count(), 1);
    QCOMPARE(LogManager::handleQtMessages(), false);

    loggingEvents()->clearList();
    mProperties.setProperty(key_handle_qt_messages,
                            QStringLiteral("true"));
    QVERIFY(PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 0);
    QCOMPARE(loggingEvents()->list().count(), 0);
    QCOMPARE(LogManager::handleQtMessages(), true);
}


void Log4QtTest::PropertyConfigurator_example()
{
    LogManager::resetConfiguration();
    resetLogging();
    mDefaultProperties.clear();
    mProperties.clear();

    QString file(mTemporaryDirectory.path() + "/PropertyConfigurator/log");

    // Based on the JavaDoc example:
    // - A1: JavaDoc uses SyslogAppender, which is not available on all platforms
    // - A2: JavaDoc does not set a file, which causes error on activation
    // - A2: JavaDoc uses default values for file size and backup index. Use
    //       different values.
    // - A2 Layout: ContextPrinting uses default enabled. Use disabled instead.
    // - root: JavaDoc uses default level DEBUG. Use INFO instead.
    // - SECURITY: JavaDoc uses INHERIT. Use INHERITED instead
    // - log4j.logger.class.of.the.day: JavaDoc uses INHERIT. Use INHERITED
    //   instead

    // Appender A1: ConsoleAppender with PatternLayout
    mProperties.setProperty(QStringLiteral("log4j.appender.A1"),
                            QStringLiteral("org.apache.log4j.ConsoleAppender"));
    mProperties.setProperty(QStringLiteral("log4j.appender.A1.Target"), QStringLiteral("System.Out"));
    mProperties.setProperty(QStringLiteral("log4j.appender.A1.layout"),
                            QStringLiteral("org.apache.log4j.PatternLayout"));
    mProperties.setProperty(QStringLiteral("log4j.appender.A1.layout.ConversionPattern"),
                            QStringLiteral("%-4r %-5p %c{2} %M.%L %x - %m\n"));
    // Appender A2: RollingFileAppender with TTCCLayout
    mProperties.setProperty(QStringLiteral("log4j.appender.A2"),
                            QStringLiteral("org.apache.log4j.RollingFileAppender"));
    mProperties.setProperty(QStringLiteral("log4j.appender.A2.File"), file);
    mProperties.setProperty(QStringLiteral("log4j.appender.A2.MaxFileSize"), QStringLiteral("13MB"));
    mProperties.setProperty(QStringLiteral("log4j.appender.A2.MaxBackupIndex"), QStringLiteral("7"));
    mProperties.setProperty(QStringLiteral("log4j.appender.A2.layout"),
                            QStringLiteral("org.apache.log4j.TTCCLayout"));
    mProperties.setProperty(QStringLiteral("log4j.appender.A2.layout.ContextPrinting"),
                            QStringLiteral("disabled"));
    mProperties.setProperty(QStringLiteral("log4j.appender.A2.layout.DateFormat"), QStringLiteral("ISO8601"));
    // Root Logger: Uses A2
    mProperties.setProperty(QStringLiteral("log4j.rootLogger"), QStringLiteral("INFO, A2"));
    // Logger SECURITY: Uses A1
    mProperties.setProperty(QStringLiteral("log4j.logger.SECURITY"), QStringLiteral("INHERITED, A1"));
    mProperties.setProperty(QStringLiteral("log4j.additivity.SECURITY"), QStringLiteral("false"));
    // Logger SECURITY.access:
    mProperties.setProperty(QStringLiteral("log4j.logger.SECURITY.access"), QStringLiteral("WARN"));
    // Logger class.of.the.day:
    mProperties.setProperty(QStringLiteral("log4j.logger.class.of.the.day"), QStringLiteral("INHERITED"));

    // No warnings, no errors expected
    QVERIFY(PropertyConfigurator::configure(mProperties));
    QCOMPARE(ConfiguratorHelper::configureError().count(), 0);
    QCOMPARE(loggingEvents()->list().count(), 0);

    // Root logger
    Logger *p_logger = LogManager::rootLogger();
    QCOMPARE(p_logger->level(), Level(Level::INFO_INT));
    QCOMPARE(p_logger->appenders().count(), 1);
    auto *p_a2 =
        qobject_cast<Log4Qt::RollingFileAppender *>(p_logger->appenders().at(0).data());
    QVERIFY(p_a2 != nullptr);
    QCOMPARE(p_a2->file(), file);
    QCOMPARE(p_a2->maximumFileSize(), Q_INT64_C(13 * 1024 * 1024));
    QCOMPARE(p_a2->maxBackupIndex(), 7);
    auto *p_a2layout =
        qobject_cast<Log4Qt::TTCCLayout *>(p_a2->layout().data());
    QVERIFY(p_a2layout != nullptr);
    QCOMPARE(p_a2layout->contextPrinting(), false);
    QCOMPARE(p_a2layout->dateFormat(), QString::fromLatin1("ISO8601"));

    // Logger SECURITY
    QVERIFY(LogManager::exists("SECURITY"));
    p_logger = LogManager::logger(QStringLiteral("SECURITY"));
    QCOMPARE(p_logger->level(), Level(Level::NULL_INT));
    QCOMPARE(p_logger->appenders().count(), 1);
    auto *p_a1 =
        qobject_cast<Log4Qt::ConsoleAppender *>(p_logger->appenders().at(0).data());
    QVERIFY(p_a1 != nullptr);
    QCOMPARE(p_a1->target(), QString::fromLatin1("STDOUT_TARGET"));
    auto *p_a1layout =
        qobject_cast<Log4Qt::PatternLayout *>(p_a1->layout().data());
    QVERIFY(p_a1layout != nullptr);
    QCOMPARE(p_a1layout->conversionPattern(), QString::fromLatin1("%-4r %-5p %c{2} %M.%L %x - %m\n"));

    // Logger SECURITY::access
    QVERIFY(LogManager::exists("SECURITY::access"));
    p_logger = LogManager::logger(QStringLiteral("SECURITY::access"));
    QCOMPARE(p_logger->level(), Level(Level::WARN_INT));

    // Logger class::of::the::day
    QVERIFY(LogManager::exists("class::of::the::day"));
}

void Log4QtTest::RollingFileAppender()
{
    resetLogging();

    QString dir(mTemporaryDirectory.path() + "/RollingFileAppender");
    QString file(QStringLiteral("/log"));

    // Using a RollingFileAppender with 2 files history and 3 messages per file
    Log4Qt::RollingFileAppender appender;
    appender.setName(QStringLiteral("RollingFileAppender"));
    appender.setFile(dir + file);
    appender.setLayout(LayoutSharedPtr(new SimpleLayout()));
    appender.setMaxBackupIndex(2);
    appender.setMaximumFileSize(40);
    appender.activateOptions();

    // Output 9 messages
    int i;
    for (i = 0; i < 10; i++)
        appender.doAppend(LoggingEvent(test_logger(), Level::DEBUG_INT,
                                       QStringLiteral("Message %1").arg(i)));

    // No warnings or errors expected
    QCOMPARE(loggingEvents()->list().count(), 0);

    // Validate diretcory
    QStringList expected;
    QString result;
    expected << QStringLiteral("log") << QStringLiteral("log.1") << QStringLiteral("log.2");
    if (!validateDirContents(dir, expected, result))
        QFAIL(qPrintable(result));

    // Validate files
    expected.clear();
    expected << QStringLiteral("DEBUG - Message 9");
    if (!validateFileContents(dir + file, expected, result))
        QFAIL(qPrintable(result));
    expected.clear();
    expected << QStringLiteral("DEBUG - Message 6") << QStringLiteral("DEBUG - Message 7")
             << QStringLiteral("DEBUG - Message 8");
    if (!validateFileContents(dir + file + ".1", expected, result))
        QFAIL(qPrintable(result));
    expected.clear();
    expected << QStringLiteral("DEBUG - Message 3") << QStringLiteral("DEBUG - Message 4")
             << QStringLiteral("DEBUG - Message 5");
    if (!validateFileContents(dir + file + ".2", expected, result))
        QFAIL(qPrintable(result));

    QCOMPARE(loggingEvents()->list().count(), 0);
}


QString Log4QtTest::dailyRollingFileAppenderSuffix(QDateTime dateTime)
{
    QString result(QStringLiteral("."));
    result += QString::number(dateTime.date().year()).rightJustified(4, '0');
    result += '-';
    result += QString::number(dateTime.date().month()).rightJustified(2, '0');
    result += '-';
    result += QString::number(dateTime.date().day()).rightJustified(2, '0');
    result += '-';
    result += QString::number(dateTime.time().hour()).rightJustified(2, '0');
    result += '-';
    result += QString::number(dateTime.time().minute()).rightJustified(2, '0');
    return result;
}


QString Log4QtTest::enumValueToKey(QObject *pObject,
                                   const char *pEnumeration,
                                   int value)
{
    Q_ASSERT(pObject);
    Q_ASSERT(!QString(pEnumeration).isEmpty());

    int i = pObject->metaObject()->indexOfEnumerator(pEnumeration);
    Q_ASSERT(i >= 0);
    QMetaEnum enumerator = pObject->metaObject()->enumerator(i);
    Q_ASSERT(enumerator.isValid());
    QString result = enumerator.valueToKey(value);
    Q_ASSERT(!result.isNull());
    return result;
}


void Log4QtTest::resetLogging()
{
    Log4Qt::Logger *p_logger;

    // Log4Qt logger
    p_logger = LogManager::logLogger();
    p_logger->setAdditivity(false);
    p_logger->setLevel(Level::WARN_INT);
    p_logger->removeAllAppenders();

    // Log4QtTest appender
    p_logger->addAppender(mpLoggingEvents);
    loggingEvents()->clearList();
    loggingEvents()->clearFilters();
    loggingEvents()->setThreshold(Level::WARN_INT);

    // Test logger
    p_logger = test_logger();
    p_logger->setAdditivity(true);
    p_logger->setLevel(Level::NULL_INT);
}


bool Log4QtTest::compareStringLists(const QStringList &actual,
                                    const QStringList &expected,
                                    const QString &entry,
                                    const QString &entries,
                                    QString &result)
{
    QString tab(QStringLiteral("   "));
    QString eol(QStringLiteral("\n"));

    // Generate content string
    QString content;
    int i;
    content += tab + "Actual: " + entries + ": "
               + QString::number(actual.count()) + eol;
    for (i = 0; i < actual.count(); i++)
        content += tab + tab + '\'' + actual.at(i) + '\'' + eol;
    content += tab + "Expected: " + entries + ": "
               + QString::number(expected.count()) + eol;
    for (i = 0; i < expected.count(); i++)
        content += tab + tab + '\'' + expected.at(i) + '\'' + eol;

    // Check count
    if (actual.count() != expected.count())
    {
        result = tab + "Compared " + entry + " counts are not the same" + eol;
        result += content;
        return false;
    }

    // Check entries
    for (i = 0; i < actual.count(); i++)
    {
        if (actual.at(i) != expected.at(i))
        {
            result = tab + entry + " " + QString::number(i + 1)
                      + " is not the same" + eol;
            result += content;
            return false;
        }
    }

    result.clear();
    return true;
}


bool Log4QtTest::deleteDirectoryTree(const QString &name)
{
    QFileInfo file_info(name);
    if (!file_info.exists())
        return true;
    if (file_info.isDir())
    {
        QDir d(name);
        const QStringList members = d.entryList(QDir::Dirs | QDir::Files
                                          | QDir::NoDotAndDotDot | QDir::NoSymLinks
                                          | QDir::Hidden, QDir::Name | QDir::DirsFirst);
        for (const auto &member : members)
            if (!deleteDirectoryTree(name + '/' + member))
                return false;
        if (d.rmdir(name))
            return true;
        qDebug() << "Unable to remove directory: " << name;
        return false;
    }

    QFile f(name);
    if (f.remove())
        return true;

    qDebug() << "Unable to remove file: " << name << "("
             << f.errorString() << ")";
    return false;
}


bool Log4QtTest::validateDirContents(const QString &name,
                                     const QStringList &expected,
                                     QString &result)
{
    QDir dir(name);
    if (!dir.exists())
    {
        result = QStringLiteral("The dir '%1' does not exist").arg(name);
        return false;
    }

    QStringList actual = dir.entryList(QDir::Dirs | QDir::Files
                                       | QDir::NoDotAndDotDot | QDir::NoSymLinks | QDir::Hidden,
                                       QDir::Name | QDir::DirsFirst);
    if (!compareStringLists(actual, expected, QStringLiteral("Entry"), QStringLiteral("Entries"), result))
    {
        QString error =
            QStringLiteral("The directory contents validation failed.\n   '%1'\n%2");
        result = error.arg(name, result);
        return false;
    }

    return true;
}


bool Log4QtTest::validateFileContents(const QString &name,
                                      const QStringList &expected,
                                      QString &result)
{
    QFile file(name);
    if (!file.exists())
    {
        result = QStringLiteral("The expected file '%1' does not exist (%2)").arg(name, file.errorString());
        return false;
    }
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        result = QStringLiteral("The expected file '%1' cannot be opened (%2)").arg(name, file.errorString());
        return false;
    }

    QStringList actual;
    QTextStream textstream(&file);
    QString line = textstream.readLine();
    while (!line.isNull())
    {
        actual << line;
        line = textstream.readLine();
    }
    if (!compareStringLists(actual, expected, QStringLiteral("Line"), QStringLiteral("Lines"), result))
    {
        QString error = QStringLiteral("The file contents validation failed.\n   '%1'\n%2");
        result = error.arg(name, result);
        return false;
    }

    return true;
}

Log4Qt::ListAppender *Log4QtTest::loggingEvents() const
{
    return qobject_cast<Log4Qt::ListAppender *>(mpLoggingEvents.data());
}

#include "moc_log4qttest.cpp"
